package main

import (
	"World/pb"
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"github.com/golang/protobuf/proto"
	"log"
	"net"
	"reflect"
	"time"
)

type MessageHandler func(conn net.Conn, header pb.MessageHeader, msg interface{})
type MessageInfo struct {
	msgType    reflect.Type
	msgHandler MessageHandler
}

var (
	msg_map = make(map[pb.MSGID]MessageInfo)
)

func RegisterMessage(msgid pb.MSGID, msg interface{}, handler MessageHandler) {

	var info MessageInfo
	info.msgType = reflect.TypeOf(msg.(proto.Message))
	info.msgHandler = handler

	msg_map[msgid] = info
}

func MsgResponseHandler(conn net.Conn, header pb.MessageHeader, msg interface{}) {
	msgID := pb.MSGID(header.MsgID)
	if msgID == pb.MSGID_MsgID_CS_World_Logon_Req {

	}
	resp := msg.(*pb.WorldLogonRsp)
	fmt.Printf("recvd message response:%+v header:%+v", *resp, header)

	HeartBeat(conn, int(resp.Uuid))
}

func HeartBeatHandler(conn net.Conn, header pb.MessageHeader, msg interface{}) {
	//msgID := pb.MSGID(header.MsgID)

	resp := msg.(*pb.WorldHeartBeatRsp)
	fmt.Printf("HeartBeatHandler recvd message response:%+v header:%+v", *resp, header)

	time.Sleep(6 * time.Second)
	go HeartBeat(conn, int(resp.Uid))
}

func init() {

	RegisterMessage(pb.MSGID_MsgID_SC_World_Logon_Rsp, &pb.WorldLogonRsp{}, MsgResponseHandler)
	RegisterMessage(pb.MSGID_MsgID_SC_World_HeartBeat_Rsp, &pb.WorldHeartBeatRsp{}, HeartBeatHandler)

}

const (
	addr                    = "192.168.11.109:11001"
	Min_Message_Size        = 16
	Max_Message_Size uint16 = 4096
)

func HeartBeat(conn net.Conn, uuid int) {
	var req pb.WorldHeartBeatReq
	req.Uid = int32(uuid)
	tmp, err := proto.Marshal(&req)
	if err != nil {
		fmt.Println("marshal proto failed:", err.Error())
		return
	}

	//dataLen = len(tmp)

	body_len := len(tmp)
	total_len := body_len + Min_Message_Size
	bys := new(bytes.Buffer)
	seq := 1

	uid := uuid

	rid := 0
	//binary.Write(bys, binary.BigEndian, uint16(total_len))
	binary.Write(bys, binary.BigEndian, uint16(total_len))
	binary.Write(bys, binary.BigEndian, uint16(pb.MSGID_MsgID_CS_World_HeartBeat_Req))
	binary.Write(bys, binary.BigEndian, uint32(seq))
	binary.Write(bys, binary.BigEndian, uint32(uid))
	binary.Write(bys, binary.BigEndian, uint32(rid))
	fmt.Println("create club body total:", body_len)
	buf := append(bys.Bytes(), tmp...)
	fmt.Println("Login len:", len(buf))

	conn.Write(buf)
	if err != nil {
		log.Println("||||||||||||||||=================ssssb err=%v", err)
	}

}

func Login(conn net.Conn, uuid int) {
	var req pb.WorldLogonReq
	//var dataLen int
	req.DeviceUuid = "c2e79cf60ecb61c6439b7aeb67b5c620"

	req.GameId = 1

	tmp, err := proto.Marshal(&req)
	if err != nil {
		fmt.Println("marshal proto failed:", err.Error())
		return
	}

	//dataLen = len(tmp)

	body_len := len(tmp)
	total_len := body_len + Min_Message_Size
	bys := new(bytes.Buffer)
	seq := 1

	uid := uuid

	rid := 0
	//binary.Write(bys, binary.BigEndian, uint16(total_len))
	binary.Write(bys, binary.BigEndian, uint16(total_len))
	binary.Write(bys, binary.BigEndian, uint16(pb.MSGID_MsgID_CS_World_Logon_Req))
	//binary.Write(bys, binary.BigEndian, uint16(12))
	binary.Write(bys, binary.BigEndian, uint32(seq))
	binary.Write(bys, binary.BigEndian, uint32(uid))
	binary.Write(bys, binary.BigEndian, uint32(rid))
	fmt.Println("create club body total:", body_len)
	buf := append(bys.Bytes(), tmp...)
	fmt.Println("Login len:", len(buf))

	conn.Write(buf)
	if err != nil {
		log.Println("||||||||||||||||=================ssssb err=%v", err)
	}
}

func dispatchRawData(conn net.Conn, header pb.MessageHeader, data []byte) {
	fmt.Printf("<-- dispatch new message header:%+v\n", header)
	var err error
	//var offdata []byte
	msgid := pb.MSGID(header.MsgID)

	if info, ok := msg_map[msgid]; ok {
		msg := reflect.New(info.msgType.Elem()).Interface()

		//offdata = offdata[:header.DataLen]
		err = proto.Unmarshal(data, msg.(proto.Message))
		if err != nil {
			log.Println("dispatchRawData proto unmarshal err:%v, msgid:%v, playerId:%v \n", err, header.MsgID, header.PlayerID)
			return
		}
		info.msgHandler(conn, header, msg)
		return
	} else {
		fmt.Println("invalid msgid from server:", msgid)
	}
}

func handleBuffer(conn net.Conn, buffer []byte, size uint32) (eat_len uint32, err error) {
	var header pb.MessageHeader
	rd := bytes.NewReader(buffer[0:Min_Message_Size])
	binary.Read(rd, binary.BigEndian, &header)

	if header.PackageLen > Max_Message_Size {
		fmt.Println("error got invalid message size:", header.PackageLen)
		eat_len = size
		err = nil
		return
	}

	if uint32(header.PackageLen) > size {
		fmt.Printf("error message not enough, buffer size:%v, pakcet len:%v header:%+v\n",
			size, header.PackageLen, header)
		eat_len = 0
		err = errors.New("error not enough")
		return
	}

	dispatchRawData(conn, header, buffer[Min_Message_Size:header.PackageLen])

	eat_len = uint32(header.PackageLen)
	err = nil
	return
}

func clientRecvLoop(conn net.Conn) {
	tmp := make([]byte, 1024)
	buf := make([]byte, 0, 1024)
	var buf_len uint32
	for {
		n, err := conn.Read(tmp)
		if err != nil {
			fmt.Println("recv conn error:", err)
			break
		}
		buf = append(buf, tmp[:n]...)
		buf_len += uint32(n)

		for buf_len >= uint32(Min_Message_Size) {
			el, err := handleBuffer(conn, buf, buf_len)
			if err != nil {
				fmt.Println("handler buffer error:", err)
				break
			} else {
				buf_len -= el
				buf = buf[el:]
			}
		}
	}
	fmt.Println("recv loop exist")
}

func main() {
	//TestMarsh()
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		fmt.Println("connect failed:", err.Error())
		return
	}

	go clientRecvLoop(conn)
	Login(conn, 100013)
	//time.Sleep(4 * time.Second)
	//go HeartBeat(conn, 10002)

	//go HeartBeat(conn, 1)

	var ch chan int = make(chan int)
	select {
	case _ = <-ch:
		//logger.Info("---I'm done----")
		break
	}
	//conn = conn
	//Login(conn)
	//	CreateClub(conn)
	//	ClubSnapshotList(conn)
	//JoinClub(conn)
	//	LeaveClub(conn)
	//	ClubCurrentBoard(conn)
	//	ClubMemberSnapshotList(conn)
	//	SearchClubInfo(conn)
	//	ModifyClubMember(conn)
	//	ModifyClubInfo(conn)
	//	RechargeClubFund(conn)
	//	GrantClubFund(conn)

	//	PurchaseClubLevel(conn)

	//	GetUserData(conn)
	//HeartBeat(conn)
	time.Sleep(time.Second * 3)

	//	SendMsg(conn)

	//	RechargeClubFund(conn)

}
